-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
-- 
-- this file licensed under the Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN \"AS IS\" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
-- PURPOSE.
-- See the Mulan PSL v2 for more details.

local skynet = require 'skynet'
local class = require 'mc.class'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local singleton = require 'mc.singleton'
local client = require 'thermal_mgmt.client'

local DRIVE_TEMP_INVALID<const> = 255 -- 硬盘温度的无效值

local storage_type = {
    HDD = 0,
    SSD = 1,
}

local disks_data_keeping = class()

function disks_data_keeping:ctor(bus)
    self.bus = bus
    self.intf = 'bmc.kepler.Systems.Storage.Drive'
    self.disks_info_changed = signal.new()
    -- storage_info 用来保存硬盘的信息：硬盘id, presence为1在位，media_type代表介质类型，temperature代表温度
    self.storage_info = {}
    self.SysHDDsMaxTemperature = 0
    self.SysSSDsMaxTemperature = 0
    self.SysAllSSDsMaxTemperature = 0
    self.DiskRowTemperatureAvailable = false -- 默认为false,存在硬盘温度获取不到
end

function disks_data_keeping:init()
    skynet.fork_loop({ count = 0 }, function()
        while true do
            skynet.sleep(1000) -- 继承V2 每10s更新一次
            self:get_all_infos()
            self:update_storage_info_in_config()
        end
    end)
end

function disks_data_keeping:set_sys_hdd_max_temp(temp)
    self.SysHDDsMaxTemperature = temp
    self.disks_info_changed:emit('hdd_temp', temp)
end

function disks_data_keeping:set_sys_ssd_max_temp(temp)
    self.SysSSDsMaxTemperature = temp
    self.disks_info_changed:emit('ssd_temp', temp)
end

function disks_data_keeping:set_sys_all_ssd_max_temp(temp)
    self.SysAllSSDsMaxTemperature = temp
    self.disks_info_changed:emit('all_ssd_temp', temp)
end

function disks_data_keeping:set_disk_temp_available(is_temp_available)
    if self.DiskRowTemperatureAvailable ~= is_temp_available then
        self.DiskRowTemperatureAvailable = is_temp_available
        self.disks_info_changed:emit('disks_temp_available', is_temp_available)
    end
end

-- 更新HDD，SSD硬盘的最大温度, 是否硬盘温度都能获取到
function disks_data_keeping:update_storage_info_in_config()
    local hdd_max_temp = 0
    local ssd_max_temp = 0
    local temp_invalid_num = 0
    local all_ssd_max_temp = 0

    for _, storage_info in pairs(self.storage_info) do
        if storage_info.presence == 0 then -- 硬盘不在位
            goto continue
        end
        -- 遍历所有SSD介质的硬盘，找到最大温度
        if storage_info.media_type == storage_type.SSD and
            storage_info.temperature ~= DRIVE_TEMP_INVALID and storage_info.temperature > all_ssd_max_temp then
            all_ssd_max_temp = storage_info.temperature
        end
        -- 此处只需要获取SAS和SATA接口类型的硬盘,NVME盘能够通过i2c单独获取到温度,不用在此处获取最大温度
        -- 0-undefined 1-parallel 2-SAS 3-SATA 4-FC 5-SAS/SATA(SATA/SAS硬盘的默认值) 6-Pcie 255-unknown
        if storage_info.protocol ~= 2 and storage_info.protocol ~= 3 then
            goto continue
        end
        -- 判断是否为M.2的硬盘，如果是就自动踢除,前缀为M.2或者SATA DOM
        -- 温度无效
        if storage_info.temperature == DRIVE_TEMP_INVALID then
            temp_invalid_num = temp_invalid_num + 1
            goto continue
        end
        if storage_info.media_type == storage_type.HDD and storage_info.temperature > hdd_max_temp then
            hdd_max_temp = storage_info.temperature
        elseif storage_info.media_type == storage_type.SSD and storage_info.temperature > ssd_max_temp then
            ssd_max_temp = storage_info.temperature
        end
        -- 其他情况不做处理

        ::continue::
    end
    if hdd_max_temp ~= self.SysHDDsMaxTemperature then
        self:set_sys_hdd_max_temp(hdd_max_temp)
    end
    if ssd_max_temp ~= self.SysSSDsMaxTemperature then
        self:set_sys_ssd_max_temp(ssd_max_temp)
    end
    if all_ssd_max_temp ~= self.SysAllSSDsMaxTemperature then
        self:set_sys_all_ssd_max_temp(all_ssd_max_temp)
    end
    if temp_invalid_num == 0 then
        self:set_disk_temp_available(true) -- 硬盘温度都能获取到
    else
        self:set_disk_temp_available(false) -- 存在硬盘温度无法获取
    end
    log:debug(
        "hdd maxTemp(%s), ssd maxTemp(%s), is_temp_avail(%s), invalid_temp_num(%s), all ssd maxTemp(%s)",
        self.SysHDDsMaxTemperature, self.SysSSDsMaxTemperature, self.DiskRowTemperatureAvailable, temp_invalid_num,
        self.SysAllSSDsMaxTemperature)
end

-- 获取所有硬盘信息
function disks_data_keeping:get_all_infos()
    local tmp_storage_info = {} -- 建立临时的table存储,防止异常导致数据丢失
    client:ForeachDriveObjects(function (obj)
        tmp_storage_info[obj.path] = {
            id = obj.Id,
            presence = obj.Presence,
            protocol = obj.Protocol,
            media_type = obj.MediaType,
            temperature = obj.TemperatureCelsius,
        }
        log:debug("Get disk(path:%s): (id: %u, presence: %u, protocol: %s, media type: %u, temperature: %u)",
            obj.path, obj.Id, obj.Presence, obj.Protocol, obj.MediaType, obj.TemperatureCelsius)
    end)
    self.storage_info = tmp_storage_info
end

return singleton(disks_data_keeping)